package org.example;

import lombok.Data;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.SourceLocation;
import org.example.dao.OrderDao;
import org.example.dao.UserDao;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Company 源辰信息
 *
 * @author navy
 * @date 2024/4/21
 * Email haijunzhou@hnit.edu.cn
 */
public class MyAopContext extends MyAnnotationConfigApplicationContext{
    List<AspectInfo> aspectInfos = new ArrayList<>();

    public MyAopContext(Class configClass)
            throws InstantiationException,
            IllegalAccessException,
            UnsupportedEncodingException,
            NoSuchMethodException {
        super(configClass);
        System.out.println("-------------------------------------------");
        List<Object> aspectList = this.getSingletonObjects()
                .values()
                .stream().filter(   // 过滤函数 => 返回 boolean
                        bean -> bean.getClass().getAnnotation(Aspect.class) != null
                ).collect(Collectors.toList());// 收集结果为 list 集合
        System.out.print("切面类: ");
        aspectList.forEach(System.out::println);

        for (Object aspectBean : aspectList) {
            for (Method method : aspectBean.getClass().getDeclaredMethods()) {
                Before before = method.getAnnotation(Before.class);
                if(before!=null){
                    String pointcut = before.value();// 获取切点
                    if(! pointcut.startsWith("execution")){
                        // 获取切点方法名  myPointcut
                        String pointcutMethodName = pointcut.substring(0, pointcut.indexOf("("));
                        // 获取切点方法  myPointcut
                        Method pointcutMethod = aspectBean.getClass().getDeclaredMethod(pointcutMethodName);
                        // 获取切方法上的切点注解
                        Pointcut pointcutMethodAnnotation = pointcutMethod.getAnnotation(Pointcut.class);
                        // 获取切点
                        pointcut = pointcutMethodAnnotation.value();
                    }
                    // 查找 aspectInfos 是否包含该切点的 info
                    final String pc = pointcut;
                    List<AspectInfo> infoList = aspectInfos.stream().filter(info -> info.pointcut.equals(pc))
                            .collect(Collectors.toList());
                    if(infoList.isEmpty()){
                        AspectInfo aspectInfo = new AspectInfo();
                        aspectInfo.setPointcut(pointcut);
                        aspectInfo.setAspectBean(aspectBean);
                        aspectInfo.getAdviceMethods().add(method);
                        aspectInfos.add(aspectInfo);
                    } else {
                        // 如果不为空, 那么就在原有切点info里面, 添加新的方法
                        infoList.get(0).getAdviceMethods().add(method);
                    }
                }

                After after = method.getAnnotation(After.class);
                if(after!=null){
                    String pointcut = after.value();// 获取切点
                    if(! pointcut.startsWith("execution")){
                        // 获取切点方法名  myPointcut
                        String pointcutMethodName = pointcut.substring(0, pointcut.indexOf("("));
                        // 获取切点方法  myPointcut
                        Method pointcutMethod = aspectBean.getClass().getDeclaredMethod(pointcutMethodName);
                        // 获取切方法上的切点注解
                        Pointcut pointcutMethodAnnotation = pointcutMethod.getAnnotation(Pointcut.class);
                        // 获取切点
                        pointcut = pointcutMethodAnnotation.value();
                    }
                    // 查找 aspectInfos 是否包含该切点的 info
                    final String pc = pointcut;
                    List<AspectInfo> infoList = aspectInfos.stream().filter(info -> info.pointcut.equals(pc))
                            .collect(Collectors.toList());
                    if(infoList.isEmpty()){
                        AspectInfo aspectInfo = new AspectInfo();
                        aspectInfo.setPointcut(pointcut);
                        aspectInfo.setAspectBean(aspectBean);
                        aspectInfo.getAdviceMethods().add(method);
                        aspectInfos.add(aspectInfo);
                    } else {
                        // 如果不为空, 那么就在原有切点info里面, 添加新的方法
                        infoList.get(0).getAdviceMethods().add(method);
                    }
                }

            }
        }

        System.out.println("所有的切点和增强:");
        aspectInfos.forEach(System.out::println);

        Map<String,Object> pointcutBeanSet = new HashMap<>();
        System.out.println("==============================");
        // 对 entry 遍历
        for (Map.Entry<String,Object> entry: this.getSingletonObjects().entrySet()) {
            Object bean = entry.getValue();
            String name = entry.getKey();
            // 遍历每个bean 的 方法
            a: for (Method method : bean.getClass().getDeclaredMethods()) {
                for (AspectInfo aspectInfo : aspectInfos) {
                    String regex = aspectInfo.pointcut
                            .replaceAll("execution\\((.+)\\)","$1")
                            .replaceAll("\\*|(\\.\\.)", ".*");
                    if(method.toString().matches(regex)){
                        System.out.println("-------------------------------------------");
                        System.out.println("regex = " + regex);
                        System.out.println("method = " + method);
                        System.out.println("method.toString().matches(regex) = " + method.toString().matches(regex));
                        // 方法签名 匹配 该表达式 => AOP 切点切到该方法了
                        // 只要有一个方法被切到, 那么整个bean 都被代理
                        pointcutBeanSet.put(name, bean);
                        break a;
                    }
                }
            }
        }
        System.out.println("被切到的bean:");
        pointcutBeanSet.forEach((name,bean)-> System.out.println(name + " :" + bean));

        pointcutBeanSet.forEach((name,bean)->{
            // 对 bean 进行 代理
            if (bean.getClass().getInterfaces().length>0) {
                // jdk 动态代理
            } else {
                // cglib 动态代理
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(bean.getClass());
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        try{
                            // 前置增强
                            doBeforeAdvice(method);
                            // 执行业务方法
                            Object res = methodProxy.invoke(bean, objects);
                            doAfterReturningAdvice(method, res);
                            return res;
                            // 返回增强
                        }catch(Throwable e){
                            doAfterThrowingAdvice(method, e);
                            // 异常增强
                            throw e;
                        }finally {
                            // 后置增强
                            doAfterAdvice(method);
                        }
                    }

                    private void doAfterAdvice(Method method) {
                    }

                    private void doAfterThrowingAdvice(Method method, Throwable e) {
                    }

                    private void doAfterReturningAdvice(Method method, Object res) {
                    }

                    private void doBeforeAdvice(Method method) {
                        for (AspectInfo aspectInfo : aspectInfos) {
                            String regex = aspectInfo.pointcut
                                    .replaceAll("execution\\((.+)\\)","$1")
                                    .replaceAll("\\*|(\\.\\.)", ".*");
                            if(method.toString().matches(regex)){
                                // 执行前置增强方法
                                for (Method adviceMethod : aspectInfo.getAdviceMethods()) {
                                    if (adviceMethod.getAnnotation(Before.class)!=null) {
                                        try {
                                            adviceMethod.invoke(aspectInfo.getAspectBean(), new JoinPoint() {
                                                @Override
                                                public String toShortString() {
                                                    return null;
                                                }

                                                @Override
                                                public String toLongString() {
                                                    return null;
                                                }

                                                @Override
                                                public Object getThis() {
                                                    return null;
                                                }

                                                @Override
                                                public Object getTarget() {
                                                    return null;
                                                }

                                                @Override
                                                public Object[] getArgs() {
                                                    return new Object[0];
                                                }

                                                @Override
                                                public Signature getSignature() {
                                                    return null;
                                                }

                                                @Override
                                                public SourceLocation getSourceLocation() {
                                                    return null;
                                                }

                                                @Override
                                                public String getKind() {
                                                    return null;
                                                }

                                                @Override
                                                public StaticPart getStaticPart() {
                                                    return null;
                                                }
                                            });
                                        } catch (Exception e) {
                                            throw new RuntimeException("前置增强方法执行错误!",e);
                                        }
                                    }
                                }
                            }
                        }
                    }
                });
                // 生产代理对象
                Object proxyBean = enhancer.create();
                // 替换原来的 被代理对象
                this.getSingletonObjects().put(name, proxyBean);
            }
        });

    }

    public static void main(String[] args) throws Exception {
        MyAopContext cxt = new MyAopContext(BeanConfig.class);
        OrderDao orderDao = cxt.getBean(OrderDao.class);
        orderDao.selectByName("test");

        UserDao userDao = cxt.getBean(UserDao.class);
        userDao.selectByName("test");


    }
}


@Data
class AspectInfo{
    String pointcut;
    List<Method> adviceMethods = new ArrayList<>();
    Object aspectBean;
}